package me.yricky.hid.desktop.device

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.yricky.usbhid.HidDevice
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.atomic.AtomicInteger

class DeviceState(
    hidDevice:HidDevice,
) {

    /**
     * 配置，外部可读写
     */
    var lockLast by mutableStateOf(true)
    var showTime by mutableStateOf(true)
    var historySize by mutableStateOf(5000)


    val dataHistory = mutableStateListOf<HidTransData>()
    private val ioScope = CoroutineScope(Dispatchers.IO)
    private var wBa:ByteArray by mutableStateOf(ByteArray(8))
    private val taskQueue = LinkedBlockingQueue<suspend CoroutineScope.()->Unit>()

    private val nextReadTime = mutableStateOf(0)
    val remainedReadTimes by nextReadTime
    var isBlockReading by mutableStateOf(false)
        private set
    var isBlockWriting by mutableStateOf(false)
        private set
    var totalActionCount by mutableStateOf(0)
        private set
    var device by mutableStateOf(hidDevice)
        private set


    init {
        prepareDeviceLoop()
    }

    fun clearHistory() = dataHistory.clear()

    fun setReadTimes(times:Int){
        nextReadTime.value = if(times > 0 ) times - 1 else times.coerceAtLeast(-1)
        if(times != 0){
            taskQueue.offer { read() }
        }
    }


    fun postWrite(ba:ByteArray){
        taskQueue.offer { write(ba) }
    }

    private fun addHistory(
        hidTransData: HidTransData
    ){
        synchronized(dataHistory){
            dataHistory.lastOrNull()?.let {
                if(it.data.contentEquals(hidTransData.data) && it.isSend == hidTransData.isSend){
                    it.repeatTimes.add(hidTransData.timestamp)
                    it.actionNum = hidTransData.actionNum
                    if(it.repeatTimes.size > historySize){
                        it.repeatTimes.removeRange(0,it.repeatTimes.size - historySize)
                    }
                }else{
                    dataHistory.add(hidTransData)
                }
                Unit
            } ?: dataHistory.add(hidTransData)
            if(dataHistory.size > historySize){
                dataHistory.removeRange(0,dataHistory.size - historySize)
            }
        }
    }

    fun refresh(newDeviceInst:HidDevice){
        if(device !== newDeviceInst && device.path == newDeviceInst.path){
            device = newDeviceInst
            prepareDeviceLoop()
        }
    }

    private fun prepareDeviceLoop(){
        ioScope.launch {
            if(!device.isOpened()){
                withContext(Dispatchers.IO){ device.open() }
                while (device.isOpened()){
                    withContext(Dispatchers.IO) {
                        taskQueue.take()
                    }.invoke(this)
                }
            }
        }
    }

    private suspend fun read(){
        withContext(Dispatchers.Main){ isBlockReading = true }
        withContext(Dispatchers.IO){ device.readBytes(wBa) }
        withContext(Dispatchers.Main){
            isBlockReading = false
            addHistory(
                HidTransData(
                    false,
                    wBa.clone()
                )
            )
            totalActionCount++
        }
        val tmpTime = nextReadTime.value
        if(tmpTime != 0 ){
            taskQueue.offer { read() }
            synchronized(nextReadTime){
                if(tmpTime > 0 && remainedReadTimes == tmpTime){
                    nextReadTime.value = tmpTime - 1
                }
            }
        }
    }

    private suspend fun write(ba:ByteArray){
        withContext(Dispatchers.Main){ isBlockWriting = true }
        withContext(Dispatchers.IO){ device.writeBytes(ba) }
        withContext(Dispatchers.Main){
            isBlockWriting = false
            addHistory(
                HidTransData(
                    true,
                    ba.clone()
                )
            )
            totalActionCount++
        }
    }

    inner class HidTransData(
        val isSend:Boolean,
        val data:ByteArray,
        var actionNum:Int = totalActionCount + 1,
        val timestamp:Long = System.currentTimeMillis(),
    ){
        val repeatTimes = mutableStateListOf<Long>()
    }
}